文章目录
  1. 1. 直接添加excel视图到ModelAndView
  2. 2. 自定义视图解析器
  3. 3. 自定义注解解析返回值

前段时间在一个项目里面发现,针对Excel的处理没有一个公用的视图,来个下载的需求就要自己去写一堆POI的东西,终于有一天给我也来了几个,还是按照以前的方式来写,写多了真心想吐,后面想想还是有必要整个解析Excel的视图了。花了一天时间,总结出来共有三种方式可以处理Excel视图。

由于spring已经提供了excel的抽象视图,所以我们直接继承过来就可以了。由于POI对excel2007和2003处理方式有些不同,所以spring4.2以上版本提供了两个excel抽象视图AbstractXlsxView(2007)、AbstractXlsView(2003)。现在又想到曾经公司的报表系统了,下载一年的销售数据,有上百万的数据在一个excel里面,打开直接卡死,后面还得分批次查询去搞,针对这种情况,程序里面其实可以一次性搞定的,分页查询、切割结果集到多个列表、多线程处理等等,所以处理的结果集最后还是放到Map里面的,以防数据量大的时候分成多个列表下载。这里还是只分析3中视图如何处理的,关于性能方面的东西,在实际项目中还是要充分考虑,这里就不分析了。下面的代码都是以excel2003进行分析。

这三种解析方式都要用到具体的Excel实现类,代码如下:

public class ExcelView extends AbstractXlsView {
    @Override
    protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        response.setHeader("Content-Disposition", "attachment;filename="+ new String((DateFormatUtils.format(new Date(), "yyyyMMddHHmmss") + ".xls").getBytes(), "iso-8859-1"));

        //分割list
        for (Entry<String, Object> e : model.entrySet()) {
            HSSFSheet sheet = (HSSFSheet) workbook.createSheet("测试");

            if (e.getValue() instanceof List) {
                List<?> dataList = (List<?>) e.getValue();
                HSSFRow rowHeader = sheet.createRow(0);
                //添加header
                rowHeader.createCell(0).setCellValue("id");
                rowHeader.createCell(1).setCellValue("名字");

                for (int start = 0; start < dataList.size(); start++) {
                    HSSFRow row = sheet.createRow(start + 1);
                    String[] rowsText = dataList.get(start).toString().split(",");
                    for (int col = 0; col < rowsText.length; col++) {
                        HSSFCell cell = row.createCell(col);
                        cell.setCellValue(rowsText[col]);
                    }
                }
            }

        }
    }
}

由于在AbstractXlsView里面,已经做了ContentType以及流的写入,所以我们直接把HSSFSheet里面的数据设置下就可以了。

直接添加excel视图到ModelAndView

这种方式和其他方式差不多,只是把ExcelView作为参数传到ModelAndView,代码如下:

@RequestMapping("/download")
public ModelAndView test() {
    ModelAndView mav = new ModelAndView();
    ExcelView excelView = new ExcelView();
    List<Student> list = new ArrayList<>();

    Student student = new Student();
    student.setId(1);
    student.setName("hello");
    list.add(student);

    student = new Student();
    student.setId(2);
    student.setName("world");
    list.add(student);

    mav.addObject("list", list);

    mav.setView(excelView);
    return mav;
}

不用做其他配置,发送一个请求就可以得到excel文件了,结果如下:

自定义视图解析器

类似于解析jsp,只要给定相应规则把请求指定到配置的解析器就可以了,代码如下:

public class ExcelViewResolver extends AbstractCachingViewResolver implements Ordered {
    private ApplicationContext context;

    private int order = Integer.MAX_VALUE;  // default: same as non-Ordered

    public void setOrder(int order) {
        this.order = order;
    }

    @Override
    public int getOrder() {
        return this.order;
    }

    @Override
    public boolean isCache() {
        return false;
    }

    /**
     * 直接在容器中配置视图,单例获取bean,不用每次新建,待优化 TODO
     */
    @Override
    protected View loadView(String viewName, Locale locale) throws Exception {
        context = super.getApplicationContext();

        if (context != null) {
            return context.getBean(viewName, View.class);
        }
        return null;
    }

}

配置文件:

<bean class="com.myspring.web.view.ExcelViewResolver">
    <property name="order" value="0"/>
</bean>

<bean id="excelView" class="com.myspring.web.view.ExcelView"/>

这种方式需要指定视图的名字为excelView,controller代码如下:

@RequestMapping("/download1")
public ModelAndView excel1() {
    //针对指定的视图解析
    ModelAndView mv = new ModelAndView("excelView");
    List<Student> list = new ArrayList<>();

    Student student = new Student();
    student.setId(1);
    student.setName("你好");
    list.add(student);

    student = new Student();
    student.setId(2);
    student.setName("世界");
    list.add(student);

    mv.addObject("list", list);
    return mv;
}

运行结果:

自定义注解解析返回值

spring解析json视图的时候只要@ResponseBody注解就可以了,也不用其他配置,非常方便。解析excel一样可以这样做。首先自定义一个注解:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Excel {

}

实现一个自定义的返回值处理器:

public class ExcelMethodProcessor implements HandlerMethodReturnValueHandler {

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), Excel.class) != null ||
                returnType.getMethodAnnotation(Excel.class) != null);
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest) throws Exception {
        mavContainer.setRequestHandled(true);
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        ExcelView view = new ExcelView();

        if (returnValue instanceof Map) {
            view.render( (Map)returnValue, request, response);
        } else {
            ModelMap model = new ModelMap();
            model.addAttribute("returnValue", returnValue);
            view.render(model, request, response);
        }
    }

}

配置文件里面需要把这个bean注入到容器:

<mvc:annotation-driven>
    <mvc:return-value-handlers>
        <bean class="com.myspring.web.view.ExcelMethodProcessor"/>
    </mvc:return-value-handlers>
</mvc:annotation-driven>

配置完之后,我们就只需在Controller层加个注解就完事儿了,非常简洁,代码如下:

@RequestMapping("/download2")
@Excel
public List<Student> excel2() {
    List<Student> list = new ArrayList<>();

    Student student = new Student();
    student.setId(1);
    student.setName("Tom");
    list.add(student);

    student = new Student();
    student.setId(2);
    student.setName("Jerry");
    list.add(student);

    return list;
}

运行结果:

文章目录
  1. 1. 直接添加excel视图到ModelAndView
  2. 2. 自定义视图解析器
  3. 3. 自定义注解解析返回值